import {
  CONTROLLER_KEY,
  getClassMetadata,
  listModule,
  Provide,
  Scope,
  ScopeEnum,
} from '@midwayjs/core';
import * as _ from 'lodash';
import { CoolEps } from '@cool-midway/core';
import * as path from 'path';

/**
 * 生成接口数据集合
 */
@Provide()
@Scope(ScopeEnum.Singleton)
export class SwaggerApi extends CoolEps {
  // 忽略的路由地址不生成接口文档
  ignoreRoutes = ['/apidoc/'];
  // 其他路由--单独生成接口文档的分组
  otherRoutes = ['/wps/', '/mqtt', '/api/'];

  group = { admin: [], app: [], api: [] } as {
    admin: SwaggerApiItem[];
    app: SwaggerApiItem[];
    api: SwaggerApiItem[];
  };

  // 初始化方法
  async initEps(source = '') {
    const entities = await this.getEntities();
    const controllers = await this.getControllers();
    const routers = await this.getRouters();

    controllers.forEach(controller => {
      const { prefix, module, curdOption } = controller;
      const entityName = curdOption?.entity?.name;
      const entityInfo = entities[entityName] || { columns: [], relations: [] };

      let moduleItem: { name: string; alias: string; list: Object[] };

      if (module) {
        const config = require(path.resolve(
          __dirname,
          `../../../modules/${module}/config`
        )).default();

        const list = {
          api: routers[prefix],
          name: entityName,
          columns: entityInfo.columns,
          relations: entityInfo.relations,
          prefix,
        };

        switch (true) {
          case !module:
          case this.ignoreRoutes.some(route => _.startsWith(prefix, route)):
            break;
          case this.otherRoutes.some(route => _.startsWith(prefix, route)):
            moduleItem = this.group[source].find((x: any) => x.name === module);
            if (!moduleItem) {
              moduleItem = { alias: '其他Api接口', name: 'api', list: [] };
              this.group[source].push(<SwaggerApiItem>moduleItem);
            }
            moduleItem.list.push(list);
            break;
          case _.startsWith(prefix, '/open/'):
            moduleItem = this.group['admin'].find(
              (x: any) => x.name === module
            );
            if (!moduleItem) {
              moduleItem = { alias: '开放接口', name: module, list: [] };
              this.group[source].push(<SwaggerApiItem>moduleItem);
            }
            moduleItem.list.push(list);
            break;
          case _.startsWith(prefix, `/${source}/`):
            if (source) {
              moduleItem = this.group[source].find(
                (x: any) => x.name === module
              );
              if (!moduleItem) {
                moduleItem = { alias: config.name, name: module, list: [] };
                this.group[source].push(moduleItem);
              }
              moduleItem.list.push(list);
            }
            break;
        }
      }
    });
  }

  /**
   * 获取所有控制器
   */
  async getControllers() {
    return listModule(CONTROLLER_KEY).map(controller =>
      getClassMetadata(CONTROLLER_KEY, controller)
    );
  }

  /**
   * 获取所有路由
   */
  async getRouters() {
    const flattenRouterTable =
      await this.midwayWebRouterService.getFlattenRouterTable();
    return _.groupBy(
      flattenRouterTable.map(item => ({
        method: item.requestMethod,
        path: item.url,
        summary: item.summary,
        prefix: item.prefix,
      })),
      'prefix'
    );
  }

  /**
   * 获取所有实体及其关联信息
   */
  async getEntities() {
    const result = {};
    const dataSourceNames = this.typeORMDataSourceManager.getDataSourceNames();

    dataSourceNames.forEach(dataSourceName => {
      const entityMetadatas =
        this.typeORMDataSourceManager.getDataSource(
          dataSourceName
        ).entityMetadatas;

      entityMetadatas.forEach(entityMetadata => {
        const columns = entityMetadata.columns
          .map(column => ({
            propertyName: column.propertyName,
            type:
              typeof column.type === 'string'
                ? column.type
                : column.type.name.toLowerCase(),
            length: column.length,
            comment: column.comment,
            nullable: column.isNullable,
          }))
          .filter(
            column =>
              !['createTime', 'updateTime'].includes(column.propertyName)
          );

        const relations = entityMetadata.relations.map(relation => ({
          type: relation.relationType,
          target: relation.type,
          propertyName: relation.propertyName,
          inverseSideProperty: relation.inverseSidePropertyPath,
        }));

        result[entityMetadata.name] = { columns, relations };
      });
    });

    return result;
  }
}
